﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;

namespace Pittypat
{
    /// <summary>
    /// 表示一个 UUID 值。
    /// </summary>
    /// <remarks>在输出为 json 时，格式为对 URL 友好的 base64 的字符串，长度为 22，其中 '+' 和 '/' 分别使用 '-' 和 '_' 替换。</remarks>
    public struct @uuid : IPrimitive
    {
        /// <summary>
        /// 将指定的字符串解析为一个 @uuid 值。
        /// </summary>
        /// <param name="s">要解析的字符串。</param>
        /// <returns>一个 @uuid 或 null。</returns>
        /// <remarks>
        /// 如果字符串是 "empty"、"zero"、"0"、"default"、"min"、"minvalue"，则解析为 <see cref="Empty"/>，如果是 "f"、"max"、"maxvalue"，则解析为 <see cref="MaxValue"/>。对于空字符串和 null，返回 null。
        /// </remarks>
        public static @uuid? Parse(string s)
        {
            if (String.IsNullOrEmpty(s))
            {
                return null;
            }

            s = s.Trim();
            if (String.IsNullOrEmpty(s))
            {
                return null;
            }

            if (s.Length < 22)
            {
                switch (s.ToLower())
                {
                    case "empty":
                    case "zero":
                    case "0":
                    case "default":
                    case "min":
                    case "minvalue":
                    case "min_value":
                        return @uuid.MinValue;
                    case "f":
                    case "max":
                    case "maxvalue":
                    case "max_value":
                        return @uuid.MaxValue;
                    default:
                        return null;
                }
            }

            if (s.Length == 22 || s.Length == 24)
            {
                s = s.Replace('-', '+').Replace('_', '/');
                s = s.Length == 22 ? s + "==" : s;
                try
                {
                    return Guid.Parse(new @binary(Convert.FromBase64String(s)).ToString("x"));
                }
                catch
                {
                    return null;
                }
            }
            else
            {
                var value = Guid.Empty;
                if (Guid.TryParse(s, out value))
                {
                    return value;
                }
            }

            return null;
        }

        /// <summary>
        /// 表示所有位都是 0 的值。这是只读字段。
        /// </summary>
        public static readonly @uuid Empty = new @uuid(Guid.Empty);

        /// <summary>
        /// 表示所有位都是 0 的值。这是只读字段。
        /// </summary>
        public static readonly @uuid MinValue = new @uuid(new Guid("00000000-0000-0000-0000-000000000000"));

        /// <summary>
        /// 表示所有位都是 F 的值。这是只读字段。
        /// </summary>
        public static readonly @uuid MaxValue = new @uuid(new Guid("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"));

        private Guid value;

        /// <summary>
        /// 初始化 @uuid 结构的新实例。
        /// </summary>
        /// <param name="value">结构的值。</param>
        public @uuid(Guid value)
        {
            this.value = value;
        }

        /// <summary>
        /// 将当前值转换为 Guid 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。</returns>
        public static implicit operator Guid(@uuid value)
        {
            return value.value;
        }

        /// <summary>
        /// 将指定的值转换为 @uuid 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。</returns>
        public static implicit operator @uuid(Guid value)
        {
            return new @uuid(value);
        }

        /// <summary>
        /// 将指定的字符串转换为 @uuid 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。</returns>
        public static implicit operator @uuid(string value)
        {
            return @uuid.Parse(value) ?? Empty;
        }

        /// <summary>
        /// 将字节数组值转换为 @uuid 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。对于 null 和长度不等于 16 的数组返回 <see cref="Empty"/>。</returns>
        public static explicit operator @uuid(byte[] value)
        {
            if (value == null || value.Length != 16)
            {
                return Empty;
            }

            return value.To<@uuid>() ?? Empty;
        }

        /// <summary>
        /// 将此值转换为字节数组。
        /// </summary>
        /// <returns>转换后的字节数组。</returns>
        public byte[] ToBinary()
        {
            return @binary.Parse("0x" + this.value.ToString("N"));
        }

        /// <summary>
        /// 比较两个值是否相等。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果两个值相等，则返回 true，否则返回 false。</returns>
        public static bool operator ==(@uuid x, @uuid y)
        {
            return x.value == y.value;
        }

        /// <summary>
        /// 比较两个值是否不相等。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果两个值不相等，则返回 true，否则返回 false。</returns>
        public static bool operator !=(@uuid x, @uuid y)
        {
            return x.value != y.value;
        }

        /// <summary>
        /// 判断两个值是否相等。
        /// </summary>
        /// <param name="obj">要比较的值。</param>
        /// <returns>如果两个值相等，则返回 true，否则返回 false。</returns>
        public override bool Equals(object obj)
        {
            if (obj is @uuid)
            {
                return this.value == ((@uuid)obj).value;
            }

            if (obj is Guid)
            {
                return this.value == (Guid)obj;
            }

            return false;
        }

        /// <summary>
        /// 返回此值的哈希代码。
        /// </summary>
        /// <returns>此值的哈希代码。</returns>
        public override int GetHashCode()
        {
            return this.value.GetHashCode();
        }

        /// <summary>
        /// 返回此值的 base64 字符串表示形式。
        /// </summary>
        /// <returns>此值的 base64 字符串表示形式。</returns>
        public override string ToString()
        {
            return this.ToString("I");
        }

        #region IPrimitive Members

        object IPrimitive.Value
        {
            get
            {
                return this.value;
            }
        }

        /// <summary>
        /// 将当前对象作为 JSON 字符串写入指定的文本流。
        /// </summary>
        /// <param name="writer">目标文本流。</param>
        /// <exception cref="ArgumentNullException">writer 是 null。</exception>
        public void Write(System.IO.TextWriter writer)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            writer.Write("\"{0}\"", this.ToString("I"));
        }

        #endregion

        #region IConvertible Members

        TypeCode IConvertible.GetTypeCode()
        {
            return TypeCode.Object;
        }

        bool IConvertible.ToBoolean(IFormatProvider provider)
        {
            return this.value != Guid.Empty;
        }

        byte IConvertible.ToByte(IFormatProvider provider)
        {
            return this.value.ToByteArray()[0];
        }

        char IConvertible.ToChar(IFormatProvider provider)
        {
            return BitConverter.ToChar(this.value.ToByteArray(), 0);
        }

        DateTime IConvertible.ToDateTime(IFormatProvider provider)
        {
            return new DateTime(((IConvertible)this).ToInt64(provider));
        }

        decimal IConvertible.ToDecimal(IFormatProvider provider)
        {
            return unchecked((decimal)BitConverter.ToDouble(this.value.ToByteArray(), 0));
        }

        double IConvertible.ToDouble(IFormatProvider provider)
        {
            return BitConverter.ToDouble(this.value.ToByteArray(), 0);
        }

        short IConvertible.ToInt16(IFormatProvider provider)
        {
            return BitConverter.ToInt16(this.value.ToByteArray(), 0);
        }

        int IConvertible.ToInt32(IFormatProvider provider)
        {
            return BitConverter.ToInt32(this.value.ToByteArray(), 0);
        }

        long IConvertible.ToInt64(IFormatProvider provider)
        {
            return BitConverter.ToInt64(this.value.ToByteArray(), 0);
        }

        sbyte IConvertible.ToSByte(IFormatProvider provider)
        {
            return unchecked((sbyte)this.value.ToByteArray()[0]);
        }

        float IConvertible.ToSingle(IFormatProvider provider)
        {
            return BitConverter.ToSingle(this.value.ToByteArray(), 0);
        }

        string IConvertible.ToString(IFormatProvider provider)
        {
            return this.ToString("I", provider);
        }

        object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        {
            return ((IConvertible)((IConvertible)this).ToInt64(provider)).ToType(conversionType, provider);
        }

        ushort IConvertible.ToUInt16(IFormatProvider provider)
        {
            return BitConverter.ToUInt16(this.value.ToByteArray(), 0);
        }

        uint IConvertible.ToUInt32(IFormatProvider provider)
        {
            return BitConverter.ToUInt32(this.value.ToByteArray(), 0);
        }

        ulong IConvertible.ToUInt64(IFormatProvider provider)
        {
            return BitConverter.ToUInt64(this.value.ToByteArray(), 0);
        }

        #endregion

        #region IComparable Members

        int IComparable.CompareTo(object other)
        {
            if (other is @uuid)
            {
                return this.value.CompareTo(((@uuid)other).value);
            }

            return 1;
        }

        #endregion

        #region IFormattable Members

        /// <summary>
        /// 使用指定的格式格式化当前实例的值。
        /// </summary>
        /// <param name="format">指定要使用的格式的 String，"i" 或 "I" 转换为 base64。</param>
        /// <param name="formatProvider">用于格式化该值的 IFormatProvider。null 表示从操作系统的当前区域设置中获取数字格式信息。</param>
        /// <returns>一个 String，包含具有指定格式的当前实例的值。</returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (format == null)
            {
                format = "I";
            }

            if (format == "i" || format == "I")
            {
                var v = new char[24];
                Convert.ToBase64CharArray(@binary.Parse("0x" + this.value.ToString("N")), 0, 16, v, 0);

                // '+'->'_', '/'->'-'
                for (var i = 0; i < 22; ++i)
                {
                    if (v[i] == '+')
                    {
                        v[i] = '-';
                    }
                    else if (v[i] == '/')
                    {
                        v[i] = '_';
                    }
                }

                return new string(v, 0, 22);
            }

            return this.value.ToString(format ?? "D", formatProvider);
        }

        /// <summary>
        /// 使用指定的格式格式化当前实例的值。
        /// </summary>
        /// <param name="format">指定要使用的格式的 String，"i" 或 "I" 转换为 base64。</param>
        /// <returns>一个 String，包含具有指定格式的当前实例的值。</returns>
        public string ToString(string format)
        {
            return this.ToString(format, null);
        }

        #endregion
    }
}
